/*
 * OpenParts
 * A dynamic-loading components framework for GWT
 * Copyright (C) 2011 Christophe Bouthier  [chris{AT}binary-gastronome{DOT}fr]
 *
 * This work is partially based on work I have done at INRIA (http://www.inria.fr) 
 * in the context of the Qualipso European Project (http://qualipso.org/),
 * The work done at INRIA is Copyright (C) 2006-2011 INRIA
 *
 * This work is distributed under the LGPL version 3
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License version 3
 * as published by the Free Software Foundation. See the GNU
 * Lesser General Public License in LGPL.txt for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package fr.openparts.OpenParts.client;

import java.util.HashSet;
import java.util.Set;

import com.google.gwt.user.client.ui.Widget;

/**
 * OPSlotProxy class represent, for an embedded view, the slot containing it. It allows messages dispatching to the real container slot.
 * <p>
 * It also dispatch directly all proxy commands to the view. A list of all proxy commands can be found in {@link OPProxyCommands}.
 * 
 * @author Christophe Bouthier [chris{AT}binary-gastronome{DOT}fr]
 * @creation.date 18 January 2011
 */
public class OPSlotProxy {

    /**
     * Slot id for this slot
     */
    protected String slotId;

    /**
     * Set of all container slot message handlers
     */
    protected Set<OPMessageFromContainerSlotHandler> handlers = new HashSet<OPMessageFromContainerSlotHandler>();

    /**
     *Pointer to the view, to transmit commands
     */
    protected Widget view;

    /**
     * Constructor, taking the slot id as parameter.
     * 
     * @param slotId
     *            the id of this slot. Shouldn't be null. Shouldn't be empty.
     */
    public OPSlotProxy(final String slotId) {
        super();

        assert slotId != null : "parameter slotId shouldn't be null";
        assert !slotId.isEmpty() : "parameter slotId shouldn't be empty";

        this.slotId = slotId;
    }

    /**
     * Attach a view to this slot proxy. This view will directly receive some commands from container slot. This method shouldn't be called directly, it is
     * automatically called when the proxy is built.
     * 
     * @param view
     *            the view to attach. Shouldn't be null.
     */
    public void attachView(final Widget view) {
        assert view != null : "parameter view shouldn't be null";

        this.view = view;
    }

    /**
     * Returns the slot id of this slot proxy.
     * 
     * @return the slot id. Cannot be null.
     */
    public String getSlotId() {
        return slotId;
    }

    /**
     * Add the given handler as handler of messages send by the container slot represented by this proxy. If the given handler is already registered, doesn't do
     * anything. Returns <code>true</code> if the given handler is correctly registered, <code>false</code> otherwise (if it was already registered).
     * 
     * @param handler
     *            the handler to register. Shouldn't be null.
     * @return <code>true</code> if the given handler is correctly registered, <code>false</code> if it was already registered.
     */
    public boolean addMessageHandler(final OPMessageFromContainerSlotHandler handler) {
        assert handler != null : "parameter handler shouldn't be null";

        return handlers.add(handler);
    }

    /**
     * Remove the given handler as handler of messages send by the container slot represented by this proxy. Doesn't do anything if the given handler wasn't
     * registered. Returns <code>true</code> if the given handler was registered, <code>false</code> otherwise.
     * 
     * @param handler
     *            the handler to unregister. Shouldn't be null.
     * @return <code>true</code> if the given handler was registered, <code>false</code> otherwise.
     */
    public boolean removeMessageHandler(final OPMessageFromContainerSlotHandler handler) {
        assert handler != null : "parameter handler shouldn't be null";

        return handlers.remove(handler);
    }

    /**
     * Dispatch the given message and its params to the container slot. The slot will transfert it to every @link {@link OPMessageFromEmbeddedViewHandler}
     * handlers that are registered on its @link {@link OPViewProxy}.
     * 
     * @param message
     *            the message to dispatch. Shouldn't be null.
     * @param params
     *            the parameters of the message. Can be empty, can be null.
     */
    public void dispatchMessageToContainerSlot(final String message, final OPParams params) {
        dispatchMessageToContainerSlot(message, params, null);
    }

    /**
     * Dispatch the given message and its params to the container slot. The slot will transfert it to every @link {@link OPMessageFromEmbeddedViewHandler}
     * handlers that are registered on its @link {@link OPViewProxy}.
     * 
     * @param message
     *            the message to dispatch. Shouldn't be null.
     * @param params
     *            the parameters of the message. Can be empty, can be null.
     * @param callback
     *            the callback called at the end of the message dispatching. Can be null.
     */
    public void dispatchMessageToContainerSlot(final String message, final OPParams params, final OPCommandCallback callback) {
        assert message != null : "parameter message shouldn't be null";

        final OPCommand command = OPCommandFactory.dispatchMessageToContainerSlotFactory(message, slotId, params);
        if (callback != null) {
            command.setCallback(callback);
        }

        OPShell.opBindingSharedInstance().scheduleCommand(command);
    }

    /**
     * Handle messages coming from the container slot. Dispatch the message to all registered {@link OPMessageFromContainerSlotHandler}s. This method shouldn't
     * be called directly, it is called automatically by container slot when
     * {@link OPViewProxy#dispatchMessageToEmbeddedView(String, OPParams, OPCommandCallback)} is called.
     * <p>
     * If the message is a proxy command, it is directly executed by the view, and not dispatched to message handlers. A list of all proxy commands can be found
     * in {@link OPProxyCommands}.
     * 
     * @param message
     *            the message to dispatch. Shouldn't be null.
     * @param params
     *            the parameters of the message. Can be empty, can be null.
     */
    void handleMessageFromContainerSlot(final String message, final OPParamsJS paramsjs) {
        assert message != null : "parameter message shouldn't be null";

        OPParams params = null;
        if (paramsjs != null) {
            params = paramsjs.toOPParams();
        }

        // if it is a proxy commands, call the view and don't propagate the message
        if (handleProxyCommands(message, params)) {
            return;
        }

        for (OPMessageFromContainerSlotHandler handler : handlers) {
            handler.handleMessageFromContainerSlot(message, params);
        }
    }

    /**
     * Check if the message is in fact a proxy command. If it is, and contains the correct parameters, then call directly the corresponding method on the view.
     * Returns true if this is the case, so that the message is not propagated to user's handlers. Returns false if the message is not a proxy command, so that
     * other handlers can get it.
     * 
     * @param message
     *            the message to test. Shouldn't be null.
     * @param params
     *            the parameters of the message. Can be empty, can be null.
     * @return true if the message is a proxy command and should not be propagated to other handlers, false if it is not and should be propagated.
     */
    private boolean handleProxyCommands(final String message, final OPParams params) {        
        if (! message.startsWith(OPProxyCommands.PREFIX_OP_PROXY_COMMANDS)) {
            // the message is not a proxy command
            return false;
        }

        // setStyleName
        if (OPProxyCommands.OP_PROXY_COMMAND_SET_STYLE_NAME.equals(message)) {
            final String value = params.getParamValue(OPProxyCommands.OP_PROXY_PARAM_VALUE);
            if (value != null) {
                view.setStyleName(value);
                return true;
            } else {
                // wrong value passed in parameter
                return false;
            }
        }
        
        // addStyleName
        if (OPProxyCommands.OP_PROXY_COMMAND_ADD_STYLE_NAME.equals(message)) {
            final String value = params.getParamValue(OPProxyCommands.OP_PROXY_PARAM_VALUE);
            if (value != null) {
                view.addStyleName(value);
                return true;
            } else {
                // wrong value passed in parameter
                return false;
            }
        }

        // removeStyleName
        if (OPProxyCommands.OP_PROXY_COMMAND_REMOVE_STYLE_NAME.equals(message)) {
            final String value = params.getParamValue(OPProxyCommands.OP_PROXY_PARAM_VALUE);
            if (value != null) {
                view.removeStyleName(value);
                return true;
            } else {
                // wrong value passed in parameter
                return false;
            }
        }
        
        // setStylePrimaryName
        if (OPProxyCommands.OP_PROXY_COMMAND_SET_STYLE_PRIMARY_NAME.equals(message)) {
            final String value = params.getParamValue(OPProxyCommands.OP_PROXY_PARAM_VALUE);
            if (value != null) {
                view.setStylePrimaryName(value);
                return true;
            } else {
                // wrong value passed in parameter
                return false;
            }
        }
        
        // addStyleDependentName
        if (OPProxyCommands.OP_PROXY_COMMAND_ADD_STYLE_DEPENDENT_NAME.equals(message)) {
            final String value = params.getParamValue(OPProxyCommands.OP_PROXY_PARAM_VALUE);
            if (value != null) {
                view.addStyleDependentName(value);
                return true;
            } else {
                // wrong value passed in parameter
                return false;
            }
        }

        // removeStyleDependentName
        if (OPProxyCommands.OP_PROXY_COMMAND_REMOVE_STYLE_DEPENDENT_NAME.equals(message)) {
            final String value = params.getParamValue(OPProxyCommands.OP_PROXY_PARAM_VALUE);
            if (value != null) {
                view.removeStyleDependentName(value);
                return true;
            } else {
                // wrong value passed in parameter
                return false;
            }
        }
        
        // setWidth
        if (OPProxyCommands.OP_PROXY_COMMAND_SET_WIDTH.equals(message)) {
            final String value = params.getParamValue(OPProxyCommands.OP_PROXY_PARAM_VALUE);
            if (value != null) {
                view.setWidth(value);
                return true;
            } else {
                // wrong value passed in parameter
                return false;
            }
        }
        
        // setHeight
        if (OPProxyCommands.OP_PROXY_COMMAND_SET_HEIGHT.equals(message)) {
            final String value = params.getParamValue(OPProxyCommands.OP_PROXY_PARAM_VALUE);
            if (value != null) {
                view.setHeight(value);
                return true;
            } else {
                // wrong value passed in parameter
                return false;
            }
        }

        // setSize
        if (OPProxyCommands.OP_PROXY_COMMAND_SET_SIZE.equals(message)) {
            final String width = params.getParamValue(OPProxyCommands.OP_PROXY_PARAM_VALUE_1);
            final String height = params.getParamValue(OPProxyCommands.OP_PROXY_PARAM_VALUE_2);
            if ((width != null) && (height != null)) {
                view.setSize(width, height);
                return true;
            } else {
                // wrong value passed in parameter
                return false;
            }
        }
        
        // setVisible
        if (OPProxyCommands.OP_PROXY_COMMAND_SET_VISIBLE.equals(message)) {
            final String value = params.getParamValue(OPProxyCommands.OP_PROXY_PARAM_VALUE);
            if (OPProxyCommands.OP_PROXY_VALUE_TRUE.equals(value)) {
                view.setVisible(true);
                return true;
            } else if (OPProxyCommands.OP_PROXY_VALUE_FALSE.equals(value)) {
                view.setVisible(false);
                return true;
            } else {
                // wrong value passed in parameter
                return false;
            }
        }
        
        // nop, it's not a proxy command
        return false;
    }

    /**
     * Check if there is a real slot attached to this slot proxy.
     * 
     * @return true if there is a real slot attached to this proxy, false otherwise,
     */
    public boolean isProxyAttachedToRealSlot() {
        final OPMessagesDispatcher dispatcher = OPMessagesDispatcher.getDispatcherAttachedToId(slotId);
        if (dispatcher == null) {
            return false;
        }
        return dispatcher.isMessageFromEmbeddedViewHandlerCallbackRegistered();
    }

    /**
     * Called when the embedded view is really attached to its container view. Call the {@link OPMessageFromContainerSlotHandler#onEmbeddedViewAttach()} method
     * on all registered {@link OPMessageFromContainerSlotHandler}s. This method shouldn't be called directly, it is called automatically when the view is
     * attached to the slot following a call to displayView.
     */
    public void onEmbeddedViewAttach() {
        for (OPMessageFromContainerSlotHandler hanlder : handlers) {
            hanlder.onEmbeddedViewAttach();
        }
    }

}
